/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                                                                           *
 *                          Copyright (c) 2010                               *
 *                  Alen Stojanov [ ioi.alen@gmail.com ]                     *
 *                Jacobs University Bremen, Bremen, Germany                  *
 *                                                                           *
 *                          All Rights Reserved.                             *
 *                                                                           *
 *    Permission to use, copy, modify and distribute this software and       *
 *    its documentation is hereby granted, provided that both the            *
 *    copyright notice and this permission notice appear in all copies of    *
 *    the software, derivative works or modified versions, and any           *
 *    portions thereof, and that both notices appear in supporting           *
 *    documentation.                                                         *
 *                                                                           *
 *    This code is distributed in the hope that it will be useful, but       *
 *    WITHOUT ANY WARRANTY; without even the implied warranty of             *
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. THE AUTHORS       *
 *    DISCLAIM ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER          *
 *    RESULTING FROM THE USE OF THIS SOFTWARE.                               *
 *                                                                           *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <QFile>
#include <QStringList>
#include <QTextStream>
#include <cfloat>

#include "tsdata.h"

TSData::TSData(QString name, enum TSDataType type, QColor color)
{
	this->name		= name;
	this->dataSize 	= 0;
	this->type 		= type;
	this->color		= color;
}

bool TSData::readData (QFile &file, TSThread* thread)
{
	if ( file.exists () && file.open(QIODevice::ReadOnly | QIODevice::Text) )	{
		if ( thread != NULL ) {
			while ( !file.atEnd() ) {
				QString line ( file.readLine() );
				thread->update(file.pos());
				processLine(line);
			}
		} else {
			while ( !file.atEnd() ) {
				QString line ( file.readLine() );
				processLine(line);
			}
		}
		calculateExtremes();
		return true;
	} else {
		return false;
	}

}

bool TSData::readData (QString &fileContent, TSThread* thread)
{
	QTextStream sin (&fileContent);
	if ( thread != NULL ) {
		while ( !sin.atEnd() ) {
			thread->update(sin.pos());
			processLine( sin.readLine() );
		}
	} else {
		while ( !sin.atEnd() ) {
			processLine( sin.readLine() );
		}
	}
	calculateExtremes();
	return true;
}

int TSData::size ()
{
	return dataSize;
}

enum TSData::TSDataType TSData::getType()
{
	return this->type;
}

void TSData::processLine (QString line)
{
	double time, value;

	/* remove comments from the lines */
	line = line.split(";").at(0);
	line = line.split("#").at(0);
	line = line.trimmed();

	if ( line != "" ) {
		QTextStream sin ( &line );
		if ( this->type == TSData::SingleColumn ) {
			sin >> value;
			x.push_back ( (double)dataSize );
		} else if ( this->type == TSData::DoubleColumn ) {
			sin >> time >>  value;
			x.push_back ( time );
		}
		y.push_back ( value );
		++dataSize;
	}
}

QColor TSData::getColor()
{
	return this->color;
}

void TSData::setColor(QColor color)
{
	this->color = color;
}

double TSData::getMaxX() const
		{
	return maxX;
		}

double TSData::getMaxY() const
		{
	return maxY;
		}

double TSData::getMinX() const
		{
	return minX;
		}

double TSData::getMinY() const
		{
	return minY;
		}

double TSData::getMinY(double rangeMinX, double rangeMaxX)
{
	int 	start		= getNearestXIndex(rangeMinX, TSData::NearestCeil);
	int 	end			= getNearestXIndex(rangeMaxX, TSData::NearestFloor);
	double resultMinY	= y.at(start);

	for ( int i = start; i <= end; ++i ) {
		if ( y.at(i) < resultMinY ) {
			resultMinY = y.at(i);
		}
	}
	return resultMinY;
}

double TSData::getMaxY(double rangeMinX, double rangeMaxX)
{
	int 	start		= getNearestXIndex(rangeMinX, TSData::NearestCeil);
	int 	end			= getNearestXIndex(rangeMaxX, TSData::NearestFloor);
	double resultMaxY  = y.at(start);

	for ( int i = start; i <= end; ++i ) {
		if ( y.at(i) > resultMaxY ) {
			resultMaxY = y.at(i);
		}
	}
	return resultMaxY;
}

double TSData::getX(int x)
{
	return this->x.at(x);
}

double TSData::getY(int y)
{
	return this->y.at(y);
}

QString TSData::getName()
{
	return this->name;
}

void TSData::calculateExtremes()
{
	maxX = x.at(0);
	minX = x.at(0);
	maxY = y.at(0);
	minY = y.at(0);
	for ( int i = 0; i < dataSize; ++i ) {
		if ( x.at(i) > maxX ) {
			maxX = x.at(i);
		}
		if ( x.at(i) < minX ) {
			minX = x.at(i);
		}
		if ( y.at(i) > maxY ) {
			maxY = y.at(i);
		}
		if ( y.at(i) < minY ) {
			minY = y.at(i);
		}
	}
}

int TSData::getNearestXIndex(double value, TSData::NearestType type) const
		{
	return getNearestXIndexBin(value, type, 0, dataSize-1);
		}

int TSData::getNearestXIndexBin(double value, TSData::NearestType type, int start, int end) const
		{
	if ( end - start <= 1 ) {
		//		if (  abs(value - x.at(start)) < 0.0001 ) {
		//			return start;
		//		} else if ( abs(value - x.at(end)) < 0.0001 ) {
		//			return end;
		//		} else
		if ( type == TSData::NearestFloor ) {
			return start;
		} else if ( type == TSData::NearestCeil ) {
			return end;
		} else {
			if ( abs(value - x.at(start)) < abs(x.at(end) - value) ) {
				return start;
			} else {
				return end;
			}
		}
	} else {
		int middle = (start + end) / 2;
		if ( x.at(middle) == value ) {
			return middle;
		} else if ( x.at(middle) > value ){
			return getNearestXIndexBin(value, type, start, middle);
		} else {
			return getNearestXIndexBin(value, type, middle, end);
		}
	}
		}

// TODO: There has to be check whether the data is normalized, also
// there has to be a map checking whether each data has been normalized with the other data
double TSData::distance (TSData* data)
{
	if ( distanceMap.contains(data) ) {
		return distanceMap.value(data);
	} else {
		double sum	= 0;
		double dis = 0;
		for (int i = 0; i < this->size(); i++) {
			dis = std::pow(data->getY(i) - this->getY(i), 2);
			Q_ASSERT ( sum <= (DBL_MAX - dis) );
			sum += dis;
		}
		dis = sqrt(sum);
		distanceMap.insert(data, dis);
		data->distanceMap.insert(this, dis);
		return dis;
	}
}

TSData* TSData::toBinned(long binSize, BinningType binType)
{
	/* create the new object */
	TSData* binned = new TSData(name, type, color);

	double binLength = (maxX - minX) / binSize;
	double x_ = minX + binLength / 2;
	double y_ = 0;

	int j = 0;
	for ( int i = 0; i < binSize; ++i)
	{
		/* the value for x is already calculated */
		binned->x.push_back(x_);

		/* calculate the average value of the particular bin */
		if ( binType == Average )
		{
			double 	y_sum = 0; int count = 0;

			if ( j > 0 && x[j-1] <= (minX + i * binLength) ) {
				/* y = kx + n */
				double k  = (y[j-1]-y[j]) / (x[j-1] - x[j]);
				double n  = y[i] - k * x[i];
				y_sum 	  += k * (minX + i * binLength) + n;
				++count;
			}

			while ( j < x.size() && x[j] < (minX + (i+1) * binLength) ) {
				y_sum += y[j];
				++count;
				++j;
			}

			if ( j < x.size()-1 && x[j+1] >= (minX + (i+1) * binLength) ) {
				/* y = kx + n */
				double k  = (y[j+1]-y[j]) / (x[j+1] - x[j]);
				double n  = y[i] - k * x[i];
				y_sum 	  += k * (minX + (i+1) * binLength) + n;
				++count;
			}

			y_ = y_sum / (double)count;
			binned->y.push_back(y_);
		}
		x += binLength;
	}
	return binned;
}


double TSData::distance (TSData& data)
{
	return distance(&data);
}

TSData::~TSData()
{

}
